#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/in.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/highmem.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/version.h>
#include <linux/spinlock.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/irqreturn.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <asm/irq.h>
#include <asm/page.h>
#include <asm/setup.h>
#include <linux/err.h>
#include <linux/module.h>
#include "fh_rsa.h"
#include <mach/io.h>
#include <mach/pmu.h>
#include <misc/rsa_api.h>
#include <linux/miscdevice.h>
#include <chip.h>


/*****************************************************************************
 * Define section
 * add all #define here
 *****************************************************************************/


struct fh_rsa_dev * g_rsa_dev_str;

static inline void write_rsa_param(void __iomem * addr,u64 val)
{
    writel_relaxed((u32)((val)&0x00000000ffffffffULL), addr);
    writel_relaxed((u32)((val>>32)&0x00000000ffffffffULL), addr + 4);
}



static void rsa_cfg_d(u64 *d, u32 wlen)
{
    u32 i;
	struct fh_rsa_dev * rsa_dev;
	rsa_dev = g_rsa_dev_str;

	for (i = 0; i < wlen; i++)
	{
        write_rsa_param(rsa_dev->regs + RSA_D_X(i), *d);
        d++;
	}
}



static void rsa_cfg_m(u64 *m, u32 wlen)
{

    u32 i;
	struct fh_rsa_dev * rsa_dev;
	rsa_dev = g_rsa_dev_str;

	for (i = 0; i < wlen; i++)
	{
        write_rsa_param(rsa_dev->regs + RSA_M_X(i), *m);
        m++;
	}



}

static void rsa_cfg_n(u64 *n, u32 wlen)
{
    
    u32 i;
	struct fh_rsa_dev * rsa_dev;
	rsa_dev = g_rsa_dev_str;

	for (i = 0; i < wlen; i++)
	{
        write_rsa_param(rsa_dev->regs + RSA_N_X(i), *n);
        n++;
	}


}



static void parsersadata(u64 *out, void* in,u32 insize) 
{
	u32 i  = 0;
    u8* s = NULL;
    u8* d = NULL;
    memset(out , 0, 512);
    if (insize <64) {
        /* E */
        out[0] = (u64)(*(u32*)in);
    } else {
        if (insize % 64 != 0)
			insize = (insize/64 +1)*64;
		s = (u8 *)(in+insize/8 -1);
		d = (u8 *)out;
        for (i = 0; i<insize/8; i++ ) {
            *d = *s;
            s--;
            d++;
        }
    }
}

static void writersadata(u64 *out, void* in,u32 insize) 
{
    
    u32 i  = 0;
    u8* s = (u8 *)(in+insize/8 -1);
    u8* d = (u8 *)out;

	memset(out , 0, insize);
    for (i = 0; i<insize/8; i++ ) {
        *d = *s;
        s--;
        d++;
    }
}


static u32 rsa_clz( u64  x )
{
    u32 j;
    u64 mask = (u64) 1 << (0x40 - 1);

    for( j = 0; j < 0x40; j++ )
    {
        if( x & mask ) break;

        mask >>= 1;
    }

    return j;
}


static u32 rsa_cal_bitlen(u64 * buff, u32 datanum) 
{
    u32 i, j;

    if( datanum == 0 )
        datanum = 1;

    for( i = datanum - 1; i > 0; i-- )
        if( buff[i] != 0 )
            break;
    
    j = 0x40 - rsa_clz( buff[i] );
    
    pr_err("rsa_cal_bitlen %x\n", ( i * 0x40 ) + j );
    
    return( ( i * 0x40 ) + j );}

int hw_rsa_modexp(void* E,unsigned int Elen,void* N, unsigned int Nlen,void* input,void* output)
{
    u32 val;
    u64 writebuf[64];
	struct fh_rsa_dev * rsa_dev;
	u32 ret;
    
#if 0
    printk(KERN_ERR "hw_rsa_public \n");
    printk(KERN_ERR "E : %x\n",*(u8 *)E);
	printk(KERN_ERR "E : %x\n",*(u8 *)(E + 1));
    printk(KERN_ERR "Elen : %x\n",Elen);
    printk(KERN_ERR "Nlen : %x\n",Nlen);
    printk(KERN_ERR "N: %x\n",*(u8 *)N);
	printk(KERN_ERR "N: %x\n",*(u8 *)(N + 1));
    printk(KERN_ERR "input : %x\n",*(u8 *)input);
	printk(KERN_ERR "input : %x\n",*(u8 *)(input + 1));
    printk(KERN_ERR "output : %x\n",output);
#endif
    if (Nlen != 4096 && Nlen != 2048) {
        return -1;
    }
	rsa_dev = g_rsa_dev_str;
 	spin_lock(&rsa_dev->lock); 
	reinit_completion(&rsa_dev->done);

	fh_pmu_reset_rsa();

	val = readl(rsa_dev->regs + RSA_CFG);
	val &= (~RSA_START);
    writel(val, rsa_dev->regs + RSA_CFG);
	
    writel(INT_EN | INT_CLR_SW, rsa_dev->regs + RSA_INT);
    writel(INT_EN,rsa_dev->regs + RSA_INT);


    if (Nlen == 4096) {
		val = readl(rsa_dev->regs + RSA_CFG);
		val |= RSA_MOD_4096;
        writel(val, rsa_dev->regs + RSA_CFG);
    	writel(0x11303F, rsa_dev->regs + RSA_ESCAN_CFG0);

    } else {
    	val = readl(rsa_dev->regs + RSA_CFG);
		val &= (~RSA_MOD_4096);
        writel(val, rsa_dev->regs + RSA_CFG);
    	writel(0x11301F, rsa_dev->regs + RSA_ESCAN_CFG0);
    }


	val = readl(rsa_dev->regs + RSA_ESCAN_CFG1);
	val &= ~(E_SCAN_BIT_CNT_CFG_MASK | WBIT_OFFSET_MASK);	
	val |= ((63 << E_SCAN_BIT_CNT_CFG_OFS) & E_SCAN_BIT_CNT_CFG_MASK);	
	val |= ((1 << (6 - 1)) & WBIT_OFFSET_MASK);
	writel(val, rsa_dev->regs + RSA_ESCAN_CFG1);


    parsersadata(writebuf, N, Nlen);
    rsa_cfg_n(writebuf, Nlen/64);

    parsersadata(writebuf, E, Elen);
    rsa_cfg_d(writebuf, Nlen/64);

	val = readl(rsa_dev->regs + RSA_ESCAN_CFG0);
	val &= ~(EBIT_LEN_MASK);
	val |= rsa_cal_bitlen(writebuf,Elen/64) << EBIT_LEN_OFS;
	writel(val, rsa_dev->regs + RSA_ESCAN_CFG0);

    parsersadata(writebuf,input,Nlen);
    rsa_cfg_m(writebuf, Nlen/64);

	val = readl(rsa_dev->regs + RSA_CFG);
	val |= RSA_START;
    writel(val, rsa_dev->regs + RSA_CFG);

	ret = wait_for_completion_timeout(&rsa_dev->done, 5000000);
	if (ret == 0) {
		pr_err("rsa timeout..\n");
		return -1;
	}

    writersadata(output,(void*)(rsa_dev->regs + RSA_RESULT0), Nlen);
	spin_unlock(&rsa_dev->lock); 
    /*pr_err("mol cal fin\n");*/
	return 0;
}



static irqreturn_t fh_rsa_interrupt(int irq, void *dev_id)
{

	u32 isr_status;
	struct platform_device *pdev = (struct platform_device *) dev_id;
	struct fh_rsa_dev *dev = platform_get_drvdata(pdev);

	isr_status = readl(dev->regs + RSA_INT);
	isr_status |= INT_CLR_SW;
	writel(isr_status, dev->regs + RSA_INT);
	complete(&(dev->done));
	/*pr_err("fh_rsa_interrupt done\n");*/
	return IRQ_HANDLED;
}

static int fh_rsa_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int fh_rsa_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static long fh_rsa_ioctl(struct file *filp, unsigned int cmd,
		unsigned long arg) {
	CIPHER_RSA_DATA_S rsa_data;
	void* E = NULL;
	void* N = NULL;
	void* input = NULL;
	void* output = NULL;
	int ret = 0;

	if (cmd == CMD_CIPHER_CALCRSA) {
		if (copy_from_user((void *) &rsa_data, (void __user*) arg,
				sizeof(rsa_data))) {
			return -EFAULT;
		}
		E = kzalloc(rsa_data.u16KLen/8 + 1, GFP_KERNEL);
		if(E == NULL) {
			return -EFAULT;
		}
		if (copy_from_user(E, (void __user*)(rsa_data.pu8K),
				rsa_data.u16KLen/8 + 1)) {
			ret = -EFAULT;
			goto e_err;
		}

		N = kmalloc(rsa_data.u16NLen/8, GFP_KERNEL);
		if(N == NULL) {
			ret = -EFAULT;
			goto e_err;
		}
		if (copy_from_user(N, (void __user*)(rsa_data.pu8N),
				rsa_data.u16NLen/8)) {
			ret = -EFAULT;
			goto n_err;
		}

		input = kmalloc(rsa_data.u32DataLen, GFP_KERNEL);
		if(input == NULL) {
			ret = -EFAULT;
			goto n_err;
		}
		if (copy_from_user(input, (void __user*)(rsa_data.pu8Input),
				rsa_data.u32DataLen)) {
			ret = -EFAULT;
			goto input_err;
		}

		output = kmalloc(rsa_data.u32DataLen, GFP_KERNEL);
		if(output == NULL) {
			ret = -EFAULT;
			goto input_err;
		}
		ret = hw_rsa_modexp(E, rsa_data.u16KLen, N,\
			rsa_data.u16NLen, input, output);
		if(ret < 0)
			goto  output_err;
		if (copy_to_user((int __user *)(rsa_data.pu8Output)\
				,output, rsa_data.u32DataLen)) {
			ret = -EFAULT;
			goto output_err;
		}
		ret = 0;
		goto output_err;
	} else {
	pr_err("%s,input cmd err\n", __func__);
	return -EINVAL;
	}

	output_err:
		kfree(output);
	input_err:
		kfree(input);
	n_err:
		kfree(N);
	e_err:
		kfree(E);
	return ret;
}

static const struct file_operations fh_rsa_fops = {
	.owner = THIS_MODULE,
	.open = fh_rsa_open,
	.release = fh_rsa_release,
	.unlocked_ioctl = fh_rsa_ioctl,
};

static struct miscdevice fh_rsa_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "fh_rsa",
	.fops = &fh_rsa_fops,
};

static int  fh_rsa_probe(struct platform_device *pdev)
{
	struct fh_rsa_dev *pdata;
	struct resource *res;
	struct device_node *np = pdev->dev.of_node;
	int err;

	pdata = kzalloc(sizeof(struct fh_rsa_dev), GFP_KERNEL);
	if (!pdata) {
		err = -ENOMEM;
		goto err_malloc;
	}
	spin_lock_init(&pdata->lock);
	init_completion(&pdata->done);
	if (np && !IS_ERR(np)) {
		pdata->regs = of_iomap(np, 0);
		if (!pdata->regs) {
			err = -EINVAL;
			goto err_iomap;
		}
		pdata->irq_no = irq_of_parse_and_map(np, 0);
		if (!pdata->irq_no) {
			err = pdata->irq_no;
			goto err_irq;
		}
	} else {
		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		if (!res) {
			dev_err(&pdev->dev, "can't fetch device resource info\n");
			goto err_iomap;
		}

		pdata->regs = ioremap(res->start, resource_size(res));
		if (pdata->regs == NULL) {
			dev_err(&pdev->dev, "ioremap resource error\n");
			goto err_iomap;
		}

		pdata->irq_no  = irq_create_mapping(NULL, platform_get_irq(pdev, 0));
		if (pdata->irq_no < 0) {
			dev_err(&pdev->dev, "rsa interrupt is not available.\n");
			goto err_irq;
		}
	}
	err = request_irq(pdata->irq_no, fh_rsa_interrupt, 0,
			  "fh-rsa", pdev);
	if (err) {
		dev_dbg(&pdev->dev, "request_irq failed, %d\n", err);
		goto err_req;
	}
	platform_set_drvdata(pdev, pdata);
	g_rsa_dev_str = pdata;

	err = misc_register(&fh_rsa_misc);
	if (err < 0) {
		pr_err("%s: ERROR: %s registration failed", __func__,
				"fh_rsa");
		err = -ENXIO;
		goto err_req;
	}
	return 0;

err_req:
	if(pdata->irq_no)
		irq_dispose_mapping(pdata->irq_no);
err_irq:
	if(pdata->regs)
		iounmap(pdata->regs);
err_iomap:
	kfree(pdata);
err_malloc:
	return err;

}


static int  fh_rsa_remove(struct platform_device *pdev)
{
	struct fh_rsa_dev *pdata = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);
	free_irq(pdata->irq_no, pdata);
	iounmap(pdata->regs);
	kfree(pdata);
	misc_deregister(&fh_rsa_misc);
	return 0;
}

static const struct of_device_id fh_rsa_of_match[] = {
	{.compatible = "fh,fh-rsa",},
	{},
};

MODULE_DEVICE_TABLE(of, fh_rsa_of_match);

static struct platform_driver fh_rsa_driver = {
	.driver = {
		.name = "fh_rsa",
		.of_match_table = fh_rsa_of_match,
	},
	.probe = fh_rsa_probe,
	.remove = fh_rsa_remove,
};

module_platform_driver(fh_rsa_driver);
MODULE_AUTHOR(" <tangyh@fullhan.com>");
MODULE_DESCRIPTION("fullhan RAS device driver");
MODULE_LICENSE("GPL");
